#ifndef PUDGE_GRFX_IMAGE_HPP
#define PUDGE_GRFX_IMAGE_HPP

#include <pudge/grfx/color.hpp>
#include <png.h>
#include <string>
#include <exception>

namespace pudge { namespace grfx {

	template<class Color> struct image
	{
		typedef unsigned int uint;

		const uint 	width_
			,		height_
			,		size_;

		Color * colors_;

		image(const uint w, const uint h)
			:	width_(w)
			,	height_(h)
			,	size_(w*h)
		{
			colors_ = new Color[size_];
		}

		~image()
		{
			delete[] colors_;
		}

		void clear(Color const& c)
		{
			for(uint x = 0; x < size_; ++x)
				colors_[x] = c;
		}

		Color * colors() { return colors_; }
		const uint width() { return width_; }
		const uint height() { return height_; }
		const uint size() { return size_; }
	};

	template<> struct image<color<RGBA,unsigned char>>
	{
		typedef color<RGBA,unsigned char> color_type;

		unsigned int width_;
		unsigned int height_;
		color_type * colors_;

		color_type * colors() { return colors_; }
		const unsigned int width() { return width_; }
		const unsigned int height() { return height_; }

		~image()
		{
			delete[] colors_;
		}

		image(const unsigned int w, const unsigned int h) 
			: 	width_(w)
			,	height_(h)
		{
			colors_ = new color_type[width_ * height_];
		}

		image(std::string const& fname)
		{
			typedef color_type Color;
			typedef image<Color> Image;

			const unsigned int PNG_BYTES_TO_CHECK = 4;

			const std::string PNG_ERROR 
				("PNG ERROR");

			const std::string ONLY_RGBA_SUPPORTED 
				("Only supports RGBA");

			const std::string ONLY_8BIT_SUPPORTED 
				("Can only load images with 8bit color");

			const std::string BAD_FILE_NAME 
				("Unable to open file");

			const std::string ONLY_PNG_SUPPORTED 
				("Only .png image files are supported.");

			FILE * fp;			// C style file :(

			png_byte chkbuf[PNG_BYTES_TO_CHECK];		// png check buffer

			if(fopen_s(&fp, fname.c_str(), "rb") != 0)  // OPEN FILE
				throw std::runtime_error(BAD_FILE_NAME);

			if(fread(chkbuf, 1, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK)
			{
				fclose(fp);
				throw std::runtime_error(ONLY_PNG_SUPPORTED);
			}

			if(png_sig_cmp(chkbuf, (png_size_t)0, PNG_BYTES_TO_CHECK) != 0)
			{
				fclose(fp);
				throw std::runtime_error(ONLY_PNG_SUPPORTED);
			}

			png_structp png_ptr;
			png_infop info_ptr;
			png_uint_32 imageWidth, imageHeight;
			int bit_depth, color_type, interlace_type;

			png_ptr = png_create_read_struct(
				PNG_LIBPNG_VER_STRING, 
				NULL,
				NULL,
				NULL );

			png_set_sig_bytes(png_ptr, PNG_BYTES_TO_CHECK);

			if(png_ptr == NULL)
			{
				fclose(fp);
				throw std::runtime_error(PNG_ERROR);
			}

			info_ptr = png_create_info_struct(png_ptr);

			if(info_ptr == NULL)
			{
				fclose(fp);
				png_destroy_read_struct(&png_ptr, NULL, NULL);
				throw std::runtime_error(PNG_ERROR);
			}

			if(setjmp(png_jmpbuf(png_ptr)))
			{
				fclose(fp);
				png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
				throw std::runtime_error(PNG_ERROR);
			}

			png_init_io(png_ptr, fp);
			png_read_info(png_ptr, info_ptr);

			png_get_IHDR(
				png_ptr, info_ptr, 
				&imageWidth, &imageHeight, 
				&bit_depth, &color_type, &interlace_type, 
				NULL, NULL );

			// Strip down to 8bit depth
			if(bit_depth != 8)
				png_set_strip_16(png_ptr);

			// convert to full RGBA
			if(color_type != PNG_COLOR_TYPE_RGB_ALPHA)
				if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
					png_set_tRNS_to_alpha(png_ptr);

			// update info
			png_read_update_info(png_ptr, info_ptr);

			if(png_get_channels(png_ptr, info_ptr) != 4)
			{
				fclose(fp);
				png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
				throw std::runtime_error(PNG_ERROR);
			}

			if(png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_RGB_ALPHA)
			{
				fclose(fp);
				png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
				throw std::runtime_error(PNG_ERROR);
			}

			if(png_get_bit_depth(png_ptr, info_ptr) != 8)
			{
				fclose(fp);
				png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
				throw std::runtime_error(PNG_ERROR);
			}

			size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);
			png_bytep* row_pointers = new png_bytep[imageHeight];  // ALLOC

			for(size_t row = 0; row < imageHeight; ++row)
				row_pointers[row] = (png_bytep)png_malloc(png_ptr, rowbytes); // ALLOC

			png_read_image(png_ptr, row_pointers);

			png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
			fclose(fp);  // CLOSE FILE

			width_ = imageWidth;
			height_ = imageHeight;
			colors_ = new Color[imageWidth * imageHeight];  // ALLOC

			for(size_t i = 0; i < imageHeight; ++i)
			{
				const size_t dpos = (imageHeight - i - 1) * imageWidth;

				for(size_t j = 0; j < imageWidth; ++j)
				{
					const size_t dsub = dpos + j;
					const size_t ssub = j * 4;

					colors_[dsub].r = row_pointers[i][ssub];
					colors_[dsub].g = row_pointers[i][ssub + 1];
					colors_[dsub].b = row_pointers[i][ssub + 2];
					colors_[dsub].a = row_pointers[i][ssub + 3];
				}
			}

			for(size_t row = 0; row < imageHeight; ++row)
				png_free(png_ptr, row_pointers[row]); // FREE
			delete row_pointers;	// FREE 
		}
	};
}}

#endif